fix: GAP-003 — refuse to silently fall back to Ollama when a credentialed provider's module is missing - #263
Draft
Brian Krabach (bkrabach) wants to merge 1 commit into
Draft
Conversation
…aled provider's module is missing Cross-platform behaviour change (not Windows-specific). Affects every Linux/macOS/WSL/Windows user of `detect_provider_from_env()` / `auto_init_from_env()` (the non-interactive auto-configure path used when stdin is not a TTY: CI, Docker, shadow environments). Old behaviour: `detect_provider_from_env()` treated "this provider's module isn't installed" identically to "no credentials set for this provider" -- both cases just `continue`d past the provider in the priority loop. A user with a valid `ANTHROPIC_API_KEY` set, but whose `provider-anthropic` module was not installed (or failed to install), silently fell through to the credential-free Ollama fallback. That choice got persisted to settings.yaml, so it wasn't even a one-time mistake -- every subsequent run kept using Ollama, with no error and no mention that a real API key was ever seen and discarded. The symptom the user actually saw was a `ConnectionError` against a local Ollama server they never set up, which is a much harder thing to debug than "you're missing a package." New behaviour: if a provider has all of its required credential env vars present but its module is not installed/importable, that is recorded and blocks the Ollama fallback. If no other candidate provider is both credentialed and installed, `detect_provider_from_env()` raises `CredentialedProviderModuleMissingError` naming the provider, the env vars that were found, and the fix (`amplifier provider install <name>`). `auto_init_from_env()` catches this specifically and prints a loud, specific error instead of quietly "succeeding" onto the wrong backend. Nothing is persisted, so the next run gets a real second chance once the module is installed. Unaffected: the genuine no-cloud-credentials case still lands on Ollama quietly, exactly as before (covered by `test_no_credentials_falls_through_to_ollama`). Unaffected: a higher-priority provider with a missing module no longer blocks a lower-priority provider that IS both credentialed and installed -- that one is still selected (`test_falls_through_to_second_credentialed_installed_provider`). Why fail loud instead of silently substituting a different provider: the user made an explicit choice by setting a specific provider's credentials. Silently overriding that choice with Ollama is a correctness bug dressed up as graceful degradation -- it changes which backend runs, which model answers, and (for anyone who assumed their cloud key was in effect) can send prompts to the wrong place entirely. An explicit, actionable error that names the exact fix is strictly better than a misleading downstream connection failure. Test evidence: 7 new tests in tests/test_provider_env_detect.py exercise detect_provider_from_env() directly (entry_points patched, not the whole function mocked away) across every branch: no credentials/no installed providers -> None; no credentials + Ollama installed -> quiet Ollama fallback (unaffected case, regression guard); credentials + module installed -> that provider; credentials + module missing (with and without Ollama available) -> raises; higher-priority module missing but lower-priority both credentialed and installed -> lower-priority one still selected; decisive regression guard asserting the function must never return "provider-ollama" once a credentialed-but-missing provider was seen. Reverting just the detect_provider_from_env() logic back to the old "treat missing module same as missing credentials" behavior (keeping the exception class defined so imports still resolve) makes exactly the 3 tests targeting the new behavior fail with "DID NOT RAISE" / "provider-ollama" == "provider-ollama", confirming they exercise the new code path and not just the pre-existing one. Full suite: 1308 passed, 1 skipped, 13 deselected, 1 xfailed (this branch only carries GAP-003 + tests, so the count is smaller than main's ~1316 -- expected). ruff clean on all three changed files. Extracted from #259, which bundles this GAP-003 fix together with four unrelated fixes (GAP-020/023/027/021) across 15 commits and 19 files under a "Windows compatibility gaps" label. This change is not Windows-gated and needs review on its own terms as a default-behaviour change for every platform. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This is a cross-platform default-behaviour change, not a Windows fix — despite being extracted from a PR (#259) that filed it under "Windows compatibility gaps." It changes how
detect_provider_from_env()/auto_init_from_env()behave for every Linux, macOS, WSL, and Windows user who hits the non-interactive auto-configure path (used whenever stdin isn't a TTY: CI, Docker, shadow/headless environments).Old behaviour
detect_provider_from_env()treated two very different situations identically — both simplycontinued past the provider in the priority loop:Concretely: a user with a valid
ANTHROPIC_API_KEYset, but whoseprovider-anthropicmodule wasn't installed (or failed to install), silently fell through the loop to the credential-free Ollama fallback. That choice was then persisted tosettings.yaml— not a one-time mistake, every subsequent run kept using Ollama. The only symptom the user ever saw was aConnectionErroragainst a local Ollama server they never set up or wanted, with nothing telling them their real API key was ever seen and discarded.New behaviour
If a provider has all of its required credential env vars present but its module is not installed, that's recorded and blocks the Ollama fallback. If no other candidate provider is both credentialed and installed,
detect_provider_from_env()raisesCredentialedProviderModuleMissingError, naming the provider, the env vars found, and the fix (amplifier provider install <name>).auto_init_from_env()catches this specifically and prints a loud, specific error instead of quietly "succeeding" onto the wrong backend. Nothing is persisted, so the very next run — once the module is installed — gets a real, working second chance.Unaffected (verified by tests, not just asserted):
Who's affected, and under what conditions
Every platform — this code path has no OS gating at all. Triggered specifically when:
Before this change, that combination silently reconfigured the user onto Ollama and wrote it to disk. That is a real behavioural change for anyone currently relying on (or unknowingly triggering) the old silent-fallback path — which is exactly why this needs to be reviewed as its own change, not folded into a Windows-labelled bundle where a reviewer would reasonably skim past it as platform-specific.
Why fail loud instead of silently substituting a different provider
The user made an explicit choice by setting a specific provider's credentials in their environment. Silently overriding that choice with Ollama isn't graceful degradation — it's a correctness bug wearing a nice costume. It changes which backend runs, which model actually answers, and for anyone who assumed their cloud key was in effect, it can send prompts to a completely different place than intended, with cost/data implications they never agreed to. An explicit, actionable error that names the exact provider, the exact env vars found, and the exact fix command is strictly better than a misleading downstream
ConnectionErrorthat gives no indication what actually happened.Test evidence
tests/test_provider_env_detect.py— 7 tests exercisingdetect_provider_from_env()directly (patching onlyentry_points, not mocking the function itself), covering every branch:NoneCredentialedProviderModuleMissingError(not "Ollama")Nonesilently)"provider-ollama"once a credentialed-but-missing provider was seenRevert-proof: reverting just the
detect_provider_from_env()logic to the old "treat missing module same as missing credentials" behavior (while leaving the exception class defined so imports still resolve) makes exactly the 3 tests targeting the new behavior fail:confirming these tests actually exercise the new code path, not just something that happened to pass either way.
Full suite:
1308 passed, 1 skipped, 13 deselected, 1 xfailed(this branch carries only this fix + its tests, so the count is smaller thanmain's ~1316 — expected and fine; what matters is zero failures).Lint:
ruff checkclean on all three changed files.Files changed
amplifier_app_cli/provider_env_detect.py— the actual GAP-003 fix: newCredentialedProviderModuleMissingError, updateddetect_provider_from_env()logic.amplifier_app_cli/commands/init.py— minimal: import the new exception, and oneexcept CredentialedProviderModuleMissingErrorhandler inauto_init_from_env()so the failure is reported clearly rather than being swallowed by the genericexcept Exceptionfallback below it.tests/test_provider_env_detect.py— new regression suite (above).Provenance
Extracted from #259 (
fix/gap-003-020-023-027-021), which bundles this fix together with four unrelated fixes (GAP-020 first-run prompt retry bound, GAP-023/GAP-027 SIGINT handling, GAP-021 history race) across 15 commits and 19 files, all under a single "five Windows compatibility gaps" label. This one isn't Windows-gated and changes default behaviour everywhere, so it's being pulled out for review on its own terms. #259 itself has been left untouched; removing the duplicated commits from it is a separate follow-up.